package com.ideaaedi.shardingdynamictable.config.sharding.support;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.ideaaedi.shardingdynamictable.entity.bo.DatasourceArgs;
import com.ideaaedi.shardingdynamictable.entity.bo.UpdateActualDataNodeBO;
import com.ideaaedi.shardingdynamictable.util.DatabaseUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 本地缓存实现
 *
 * @author JustryDeng
 * @since 2022/10/7 14:43:26
 */
@Slf4j
@Component
public class LocalCacheShardingTableSupport implements ShardingTableSupport, SmartInitializingSingleton {
    
    @Resource
    DatasourceArgs datasourceArgs;
    
    @Resource
    RefreshActualDataNodesSupport refreshActualDataNodesSupport;
    
    /**
     * 缓存实现
     * <br />
     * key - 逻辑表名
     * value - 真实表名集合
     */
    private static final ConcurrentHashMap<String, Set<String>> LOGIC_REAL_TABLE_NAME_CACHE = new ConcurrentHashMap<>(64);
    
    /**
     * 表ddl
     * <br />
     * key - 逻辑表名
     * value - 对应的ddl
     */
    public static final ConcurrentHashMap<String, String> TABLE_DDL_MAP = new ConcurrentHashMap<>(64);
    
    static {
        // trans_record表
        TABLE_DDL_MAP.put("trans_record", "CREATE TABLE IF NOT EXISTS `" + "%s" + "`  ("
                                          + "`id` bigint(20) UNSIGNED NOT NULL COMMENT 'id',"
                                          + "`trans_date` varchar(10) NOT NULL COMMENT '交易日期（yyyy-MM-dd）',"
                                          + "`remark` varchar(100) NULL DEFAULT NULL COMMENT '备注',"
                                          + "`del` tinyint(1) NULL DEFAULT 0 COMMENT '是否已删除(0-未删除；1-已删除)',"
                                          + "`created_by` bigint(20) NULL DEFAULT NULL COMMENT '创建人',"
                                          + "`created_at` datetime NULL DEFAULT NULL COMMENT '创建时间',"
                                          + "`updated_by` bigint(20) NULL DEFAULT NULL COMMENT '修改人',"
                                          + "`updated_at` datetime NULL DEFAULT NULL COMMENT '修改时间',"
                                          + "PRIMARY KEY (`id`) USING BTREE"
                                          + ") ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '交易记录表' ROW_FORMAT = DYNAMIC;");
    }
    
    @Override
    public void initRealTableName(String logicTableName, Set<String> realTableNameSet) {
        if (CollectionUtils.isEmpty(realTableNameSet)) {
            return;
        }
        LOGIC_REAL_TABLE_NAME_CACHE.put(logicTableName, realTableNameSet);
        log.info("initRealTableName -> {}", JSON.toJSONString(LOGIC_REAL_TABLE_NAME_CACHE));
    }
    
    @NonNull
    @Override
    public Set<String> getRealTableName(DatasourceArgs datasourceArgs, String logicTableName) {
        return LOGIC_REAL_TABLE_NAME_CACHE.computeIfAbsent(logicTableName, k -> new HashSet<>());
    }
    
    @NonNull
    @Override
    public Set<String> addRealTableName(DatasourceArgs datasourceArgs, String logicTableName, String realTableName) {
        final Set<String> existRealTableNameSet = LOGIC_REAL_TABLE_NAME_CACHE.computeIfAbsent(logicTableName, k -> new HashSet<>());
        if (!existRealTableNameSet.contains(realTableName)) {
            // need refresh actual-data-nodes
            TreeSet<String> existAllRealTables = new TreeSet<>(existRealTableNameSet);
            existAllRealTables.add(realTableName);
            refreshActualDataNodes(datasourceArgs, logicTableName, existAllRealTables);
        }
        existRealTableNameSet.add(realTableName);
        return existRealTableNameSet;
    }

    @NonNull
    @Override
    public Set<String> addAllRealTableName(DatasourceArgs datasourceArgs, String logicTableName, Set<String> realTableNameSet) {
        final Set<String> existRealTableNameSet = LOGIC_REAL_TABLE_NAME_CACHE.computeIfAbsent(logicTableName, k -> new HashSet<>());
        if (!existRealTableNameSet.containsAll(realTableNameSet)) {
            // need refresh actual-data-nodes
            TreeSet<String> existAllRealTables = new TreeSet<>(existRealTableNameSet);
            existAllRealTables.addAll(realTableNameSet);
            refreshActualDataNodes(datasourceArgs, logicTableName, existAllRealTables);
        }
        existRealTableNameSet.addAll(realTableNameSet);
        return existRealTableNameSet;
    }
    
    /**
     * 获取真实表的ddl
     *
     * @param logicTableName
     *            逻辑表名
     * @param realTableName
     *            真实表名
     *
     * @return 真实表的ddl
     */
    @NonNull
    @Override
    public String obtainRealTableDdl(DatasourceArgs datasourceArgs, String logicTableName, String realTableName) {
        if (StringUtils.isBlank(logicTableName)) {
            throw new IllegalArgumentException("logicTableName cannot be blank.");
        }
        if (StringUtils.isBlank(realTableName)) {
            throw new IllegalArgumentException("realTableName cannot be blank.");
        }
        if (!TABLE_DDL_MAP.containsKey(logicTableName)) {
            throw new IllegalStateException("Cannot obtain '" + logicTableName + "'s ddl.");
        }
        return String.format(TABLE_DDL_MAP.get(logicTableName), realTableName);
    }
    
    /**
     * 启动的时候刷新所有节点
     */
    @Override
    public void afterSingletonsInstantiated() {
        for (Map.Entry<String, String> entry : TABLE_DDL_MAP.entrySet()) {
            final String logicTableName = entry.getKey();
            TreeSet<String>  existAllRealTables = DatabaseUtil.obtainTableNameByPrefix(datasourceArgs, logicTableName + "_");
            if (CollectionUtils.isEmpty(existAllRealTables)) {
                continue;
            }
            // 初始化所有真实表到缓存
            initRealTableName(logicTableName, existAllRealTables);
            // 刷新actual-data-nodes
            refreshActualDataNodes(datasourceArgs, logicTableName, existAllRealTables);
        }
    }
    
    /**
     * 刷新actual-data-nodes
     */
    private void refreshActualDataNodes(DatasourceArgs datasourceArgs, String logicTableName, TreeSet<String> existAllRealTables) {
        if (CollectionUtils.isEmpty(existAllRealTables)) {
            existAllRealTables = DatabaseUtil.obtainTableNameByPrefix(datasourceArgs, logicTableName + "_");
        }
        if (CollectionUtils.isEmpty(existAllRealTables)) {
            log.warn("logic-table {}'s real-table is empty. skip refresh actual-data-nodes", logicTableName);
            return;
        }
        UpdateActualDataNodeBO bo = new UpdateActualDataNodeBO();
        bo.setLogicTableName(logicTableName);
        bo.setDsAndRealTablesPairList(Lists.newArrayList(Pair.of(datasourceArgs, new TreeSet<>(existAllRealTables))));
        refreshActualDataNodesSupport.refresh(bo);
    }
    
}
